package com.spring.nio.core.reactor.acceptor;

import com.google.common.collect.Lists;
import com.spring.nio.common.BasicConstant;
import com.spring.nio.core.channel.context.SimpleChannelContext;
import com.spring.nio.core.channel.context.SocketChannelContext;
import com.spring.nio.core.channel.pipeline.DefaultChannelPipeLine;
import com.spring.nio.core.configuration.AcceptorEntity;
import com.spring.nio.core.configuration.NioServerBootstrap;
import com.spring.nio.core.configuration.SelectorEntity;
import com.spring.nio.core.reactor.selector.SelectorTypeEnum;
import com.spring.nio.service.chat.handler.ChatDecodeHandler;
import com.spring.nio.service.chat.handler.ChatRealDataHandler;
import com.spring.nio.util.LocalTime;
import com.spring.nio.util.NioUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.LockSupport;

/**
 * @author: YSFY
 * @date: 2021-03-24 18:38
 * @pacage_name: com.spring.nio.core.reactor.acceptor
 * @class_name: MainChatAcceptor
 * @description: 基于聊天服务的Acceptor事件线程
 */

@Slf4j
@SuppressWarnings("all")
public class MainChatAcceptor extends AbstractNioAcceptor implements Runnable {
    private AcceptorEntity acceptorEntity;
    private Selector selector;
    public void setAcceptorEntity(AcceptorEntity acceptorEntity) {
        this.acceptorEntity = acceptorEntity;
    }

    @Override
    public void run() {
        try {
            this.selector = ((SelectorEntity) NioServerBootstrap.getConfig().getSelectors().get(SelectorTypeEnum.CHAT_SELECTOR.getName())).getSelector();
            ServerSocketChannel serverSocketChannel = super.initServerSocketChannel(this.selector, this.acceptorEntity.getAcceptorName());

            if(!serverSocketChannel.isOpen() || ! serverSocketChannel.isRegistered()) {
                throw new RuntimeException("ServerSocketChannel is not open or registered");
            }

            log.info("--> MainChatAcceptor: 接入通道服务Acceptor开始进行监听, 监听目标 [{}]", this.acceptorEntity.getAcceptorName());
            while (true) {
                SocketChannel socketChannel = serverSocketChannel.accept();
                if(Optional.ofNullable(socketChannel).isPresent()) {
                    this.enhance(socketChannel);
                }

                LockSupport.park();
            }
        } catch (Exception e) {
            log.error("--> MainChatAcceptor | --> 聊天服务端acceptor监听处理异常, error = {}", e.getMessage());
        }
    }

    @Override
    public void enhance(Channel channel) throws IOException {
        SocketChannel socketChannel = (SocketChannel) channel;
        SocketChannelContext socketChannelContext = createModel(socketChannel);
        NioUtil.eventRegister(socketChannel, this.selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE, socketChannelContext);
        log.info("--> MainChatAcceptor: 成功接入 [{}] 通道, remoteAddress = {}", this.acceptorEntity.getAcceptorName(), socketChannelContext.getRemoteAddress());
    }

    /**
     * 创建SocketChannel通道附件
     * @param socketChannel
     * @return
     * @throws IOException
     */
    private SocketChannelContext createModel(SocketChannel socketChannel) throws IOException {
        // 1、配置SocketChannel基本参数
        socketChannel.configureBlocking(false);
        // 是否启用TCP心跳机制 true：启用
        socketChannel.socket().setKeepAlive(true);
        // 是否一有数据就马上发送。 true：启用
        socketChannel.socket().setTcpNoDelay(false);
        /**
         * 优雅的关闭socket连接，并发送-1 (优雅地关闭套接字，或者立刻关闭)
         * 这个Socket选项可以影响close方法的行为。
         *  在默认情况下，当调用close方法后，将立即返回；
         *      1、如果这时仍然有未被送出的数据包，那么这些数据包将被丢弃。
         *      2、如果将linger参数设为一个正整数n时(n的值最大是65,535)，在调用close方法后，将最多被阻塞n秒。在这n秒内，系统将尽量将未送出的数据包发送出去；
         *          2.1、如果超过了n秒，如果还有未发送的数据包，这些数据包将全部被丢弃；而close方法会立即返回。
         *          true, linger > 0   配置该种方式，主动关闭一方关闭后，此时对端发起连续read调用会读取到-1,如果发起连续write,第一次正常发送，再次write会触发SocketException: broken pipe(write failed)
         *
         *          未设置soLinger选项  配置该种方式，主动关闭一方关闭后，此时对端发起连续read调用会读取到-1,如果发起连续write,第一次正常发送，再次write会触发SocketException: broken pipe(write failed)
         *      3、setSoLinger(true, 0):
         *          true, linger = 0，此时会触发主动关闭的socket丢弃其对应的发送缓冲区的数据，并立即向对端发送rst(连接已被重置的包)，那么
         *              对端如果继续进行socket.read调用，将会立即产生SocketException: connect reset(read)
         *              对端如果继续进行socket.write调用，将会立即产生SocketException: broken pipe(write failed)
         * */
        socketChannel.socket().setSoLinger(true, 10);
        // 对ServerSocket来说表示等待连接的最长空等待时间; 对Socket来说表示读数据最长空等待时间。
        socketChannel.socket().setSoTimeout(Integer.parseInt(NioUtil.getValue(BasicConstant.SOCKET_TIMEOUT)));
        socketChannel.socket().setReceiveBufferSize(Integer.parseInt(NioUtil.getValue(BasicConstant.SOCKET_RECEIVE_BUFFER_SIZE)));
        socketChannel.socket().setSendBufferSize(Integer.parseInt(NioUtil.getValue(BasicConstant.SOCKET_SEND_BUFFER_SIZE)));

        // 2、封装当前通道socketChannel上下文对象
        SocketChannelContext socketChannelContext = new SocketChannelContext();
        socketChannelContext.setLocalAddress(NioUtil.getLocalAddress(socketChannel));
        socketChannelContext.setRemoteAddress(NioUtil.getRemoteAddress(socketChannel));
        socketChannelContext.setSocketChannel(socketChannel);
        socketChannelContext.setChannelPipeLine(new DefaultChannelPipeLine());
        socketChannelContext.setBlockingQueue(new LinkedBlockingQueue<>(1000));
        socketChannelContext.getChannelPipeLine().addHandler(new SimpleChannelContext(), new ChatDecodeHandler());
        socketChannelContext.getChannelPipeLine().addHandler(new SimpleChannelContext(), new ChatRealDataHandler());
        socketChannelContext.setRealList(Lists.newArrayList());
        socketChannelContext.setByteBuffer(ByteBuffer.allocateDirect(Integer.parseInt(NioUtil.getValue(BasicConstant.BYTEBUFFER))));
        return socketChannelContext;
    }

}
